/**
 * bind() 方法的作用是将函数绑定至某个对象，bind() 方法的返回值是一个新的函数对象。
 *  */

// 将 f() 函数调用 bind() 方法绑定至对象 o，用变量 g 来接收 bind() 返回的函数
// (以函数调用形式)调用 g 时，会将原始函数 f 当做对象 o 的方法来使用。

var f = function (y) {
    return this.x + y;
}
var o = {
    x: 2
}
var g = f.bind(o) // 将 f() 绑定到 o 对象上

// 以函数调用的方式调用 g(x)，相当于调用 o.f(x)
console.log(g(6));

// 实现 bind() 绑定
function bind(f, o) {
    if (f.bind) {
        // 如果 bind() 方法存在，使用 bind() 方法
        return f.bind(o)
    } else {
        return function () {
            // 利用 apply() 使 o 对象来调用 f() 方法，并且传入类数组对象参数 arguments
            return f.apply(o, arguments) // arguments 是调用绑定函数时传入的参数
        }
    }
}

/**
 * bind() 第一个实参是要绑定方法的对象（本质是将函数的 this 指向改为传入的对象），同时后面的实参也会绑定至 this，函数式编程中的柯里化
 * 
 * 柯里化就是把一个带有多个参数的函数拆分成一系列带部分参数的函数。
 * 
 * 柯里化所要表达是: 如果你固定某些参数，你将得到接受余下参数的一个函数
 * 
 * 柯里化具有：延迟计算、参数复用、动态生成函数的作用。
 * 
 * js 柯里化是逐步传值，逐步缩小函数的适用范围，逐步求解的过程。
 *  */

var sum = function (x, y) {
    return x + y
}
// 创建一个类似sum的新函数，但是 this 绑定到 null
// 并且第一个参数绑定为1，新的函数只期望传入一个参数

var g = sum.bind(null, 1) // 将sum的第一个参数 x 绑定为1

console.log(g(3));  // ==> 4，因为x绑定为1，将3作为参数传入y


function f(y, z) {
    return this.x + y + z
}
// 将f函数绑定到对象{x: 2}，将3绑定到函数的第一个参数y，新创建的函数传入一个参数
var g = f.bind({x: 2}, 3)
console.log(g(1)); // 6

// 模拟实现 bind() 方法：bind() 方法返回的是一个闭包

// 不支持bind方法
if (!Function.prototype.bind) {
    Function.prototype.bind = function(o) {
        // 保存 bind() 中的 this 与 arguments，便于在嵌套函数中使用
        var self = this
        var boundArgs = arguments

        //  bind() 方法返回一个函数对象
        return function () {
            // 创建一个实参列表，将传入 bind() 的第二个及以后的实参都传入这个函数
            var args = []
            // 传入 bind() 函数的参数处理，从第二位开始
            for (var i = 1; i < boundArgs.length; i++) {
                args.push(boundArgs[i])
            }
            // 将调用新函数时传入的参数继续添加到 args
            for (var j = 0; j < arguments.length; j++) {
                args.push(arguments[j])
            }

            // 将 self 作为 o 的方法来调用
            self.apply(o, args)
        }
    }
}
/**
 * bind()方法的某些特性是上述模拟方法不能替代的。
 * 
 * 1. bind()方法返回一个真正的函数对象，函数对象的length属性是绑定函数的形参个数减去绑定的实参个数（length的值不能小于0）
 * 
 * 2. ES5的 bind()方法可以顺带做构造函数，此时将会忽略传入 bind()方法的this，原始函数以构造函数的形式调用，其实参已经绑定。
 * 
 * 3. bind()方法返回的函数并不包含prototype属性（普通函数的固有prototype属性是不能删除的）；
 *    并且将绑定的函数用作构造器函数时所创建的对象，从原始为绑定的构造器函数中继承prototype
 */
// 绑定函数f的形参个数时2
function f (y, z) {
    return this.x + y + z;
}  
// 绑定的实参个数是1（从第二位开始是传入绑定函数的实参），即将3传递给f的第一个参数y
var g = f.bind({x: 2}, 3); 

g(1);  // ==> 6，继续将1传递给函数f的形参z

// 如果将g()作为构造函数，其创建的对象与直接利用f当做构造函数创建的对象原型是同一个prototype
h = new g()
j = new f()
console.log(h.__proto__ === j.__proto__); // true
